#' @title Plot JIP test data measured by LI-6800
#'
#' @description A enhanced function to visualize JIP test data with improved
#' customization, better default settings, and clearer data presentation.
#' Graphical preview of all data curves in log axis (BLUE > 1.2.2) with
#' normalized signals.
#'
#' @param df Data frame: files read by \code{read_induction} or \code{read_all_induction}
#' @param def_pch Numeric/vector: Point character(s) for plotting (default: 19)
#' @param alpha Numeric: Transparency degree of colors (0-1, default: 0.6)
#' @param legend_pos Character/numeric: Legend position (default: "topleft").
#'   Can be "topleft", "topright", "bottomleft", "bottomright", "top", "bottom",
#'   "left", "right", "center" or a numeric vector of length 2 (x,y coordinates)
#' @param leg_cex Numeric: Text size of legend (default: 0.6)
#' @param leg_point_cex Numeric: Point size in legend (default: 0.9)
#' @param leg_bty Character: Box type for legend ("n" = no box, "o" = box, default: "n")
#' @param xlim Numeric vector: Limits for the X axis (default: NULL, auto-calculated)
#' @param ylim Numeric vector: Limits for the Y axis (default: c(0, 1.1) for better visibility)
#' @param xlab Character: X axis label (default: "Time (ms)")
#' @param ylab Character: Y axis label (default: "Normalized fluorescence signal")
#' @param col Character/vector: Custom colors for groups (default: NULL, uses palette.colors)
#' @param log Character: Logarithmic axis ("x" for X-axis only, default: "x";
#'   use "" for no log axis)
#' @param xmark Expression/numeric: X tick mark labels (default: scientific notation)
#' @param xat Numeric: Positions for X axis tick marks (default: c(0.001, 0.01, 0.1, 1, 10, 100, 1000))
#' @param use_PAM Logical: Whether to use PAM signal (NORM_FLUOR) instead of DC signal (NORM_DC)
#'   (default: FALSE)
#' @param add_leg Logical: Whether to add legend (default: TRUE)
#' @param add_grid Logical: Whether to add grid lines (default: TRUE)
#' @param grid_lty Numeric: Grid line type (default: 3, dashed)
#' @param grid_col Character: Grid line color (default: "gray80")
#' @param main Character: Plot title (default: NULL)
#' @param main_cex Numeric: Title size (default: 1.2)
#' @param axis_cex Numeric: Axis text size (default: 0.9)
#' @param labs_cex Numeric: Axis label size (default: 1.0)
#' @param line_width Numeric: Line width for points (default: 0.5)
#' @param show_labels Logical: Whether to show sample labels next to curves (default: FALSE)
#' @param label_cex Numeric: Size of sample labels (default: 0.7)
#' @param label_offset Numeric: Offset for sample labels (default: 0.02)
#' @param ... Additional parameters passed to \code{plot} and \code{legend}
#'
#' @importFrom graphics points abline legend text axis grid par
#' @importFrom grDevices palette.colors adjustcolor
#' @importFrom stats na.omit
#' @export
#'
#' @examples
#' \dontrun{
#' # Basic plot
#' plot.jip(df)
#'
#' # With custom colors and title
#' plot.jip(df, col = c("red", "blue", "green"), main = "JIP Test Results")
#'
#' # Use PAM signal, show sample labels, change legend position
#' plot.jip(df, use_PAM = TRUE, show_labels = TRUE, legend_pos = "bottomright")
#'
#' # Custom grid and axis settings
#' plot.jip(df, grid_lty = 1, grid_col = "gray50", axis_cex = 1.0)
#' }

plot.jip = function(df,
                     def_pch = 19,
                     alpha = 0.6,
                     leg_bty = "n",
                     leg_cex = 0.6,
                     leg_point_cex = 0.9,
                     legend_pos = "topleft",
                     xlim = NULL,
                     ylim = c(0, 1.1),  # Extended for better visibility
                     xlab = "Time (ms)",
                     ylab = "Normalized fluorescence signal",
                     col = NULL,
                     log = "x",
                     xmark = c(expression(10^{-3}), expression(10^{-2}), expression(10^{-1}),
                               expression(10^{0}), expression(10^{1}), expression(10^2), expression(10^3)),
                     xat = c(0.001, 0.01, 0.1, 1, 10, 100, 1000),
                     use_PAM = FALSE,
                     add_leg = TRUE,
                     add_grid = TRUE,
                     grid_lty = 3,
                     grid_col = "gray80",
                     main = NULL,
                     main_cex = 1.2,
                     axis_cex = 0.9,
                     labs_cex = 1.0,
                     line_width = 0.5,
                     show_labels = FALSE,
                     label_cex = 0.7,
                     label_offset = 0.02,
                     ...) {

  # --------------------------
  # Input Validation
  # --------------------------
  if (!is.data.frame(df)) {
    stop("df must be a data frame from read_induction or read_all_induction")
  }
  
  # Check required columns
  required_cols = c("SOURCE", "MILLI_SEC")
  signal_col = if (use_PAM) "NORM_FLUOR" else "NORM_DC"
  required_cols = c(required_cols, signal_col)
  
  missing_cols = setdiff(required_cols, colnames(df))
  if (length(missing_cols) > 0) {
    stop(paste("Data frame missing required columns:", paste(missing_cols, collapse = ", ")))
  }
  
  # Remove NA values
  df = na.omit(df[, required_cols])
  
  if (nrow(df) == 0) {
    stop("No valid data after removing NA values")
  }

  # --------------------------
  # Prepare Group Information
  # --------------------------
  df$SOURCE = as.factor(df$SOURCE)
  groups = levels(df$SOURCE)
  n_groups = length(groups)
  
  # --------------------------
  # Color Preparation
  # --------------------------
  if (is.null(col)) {
    # Use distinct palette, handle more groups by recycling
    col = palette.colors(n = n_groups, palette = "Set1", alpha = alpha, recycle = TRUE)
  } else {
    # Ensure color vector matches number of groups
    if (length(col) != n_groups) {
      warning(paste("Color vector length (", length(col), ") does not match number of groups (", 
                    n_groups, "). Recycling colors.", sep = ""))
      col = rep(col, length.out = n_groups)
    }
    # Apply alpha if not already applied
    col = adjustcolor(col, alpha.f = alpha)
  }

  # --------------------------
  # Point Character Preparation
  # --------------------------
  if (length(def_pch) != 1 && length(def_pch) != n_groups) {
    warning(paste("pch vector length (", length(def_pch), ") does not match number of groups (", 
                  n_groups, "). Using default pch = 19.", sep = ""))
    def_pch = 19
  }
  pch_vec = if (length(def_pch) == 1) rep(def_pch, n_groups) else def_pch

  # --------------------------
  # Axis Limit Preparation
  # --------------------------
  # X axis limits (ensure log-compatible)
  if (is.null(xlim)) {
    x_min = min(df$MILLI_SEC, na.rm = TRUE)
    x_max = max(df$MILLI_SEC, na.rm = TRUE)
    # Add small buffer for log scale
    xlim = c(x_min * 0.9, x_max * 1.1)
  } else {
    if (any(xlim <= 0) && log == "x") {
      warning("xlim contains non-positive values with log='x'. Adjusting to positive values.")
      xlim[xlim <= 0] = min(df$MILLI_SEC[df$MILLI_SEC > 0], na.rm = TRUE) * 0.9
    }
  }

  # Y axis limits (auto-adjust if needed)
  if (is.null(ylim)) {
    y_min = min(df[[signal_col]], na.rm = TRUE) * 0.9
    y_max = max(df[[signal_col]], na.rm = TRUE) * 1.1
    ylim = c(max(y_min, 0), y_max)  # Ensure y >= 0
  }

  # --------------------------
  # Plot Setup
  # --------------------------
  # Save original par settings
  old_par = par(no.readonly = TRUE)
  on.exit(par(old_par))  # Restore par settings on exit

  # Set plot margins and text sizes
  par(
    mar = c(4.5, 4.5, 3, 1.5) + 0.1,  # Adjust margins for better labels
    cex.main = main_cex,
    cex.axis = axis_cex,
    cex.lab = labs_cex
  )

  # --------------------------
  # Create Base Plot
  # --------------------------
  plot(
    x = df$MILLI_SEC,
    y = df[[signal_col]],
    log = log,
    xlim = xlim,
    ylim = ylim,
    xlab = xlab,
    ylab = ylab,
    col = col[df$SOURCE],
    pch = pch_vec[df$SOURCE],
    lwd = line_width,
    xaxt = "n",
    main = main,
    type = "n",  # Initialize empty plot first for better control
    ...
  )

  # Add grid (improved visibility)
  if (add_grid) {
    grid(
      nx = NA,  # Auto x grid
      ny = NULL,  # Manual y grid for better spacing
      lty = grid_lty,
      col = grid_col,
      lwd = 0.8
    )
    # Add vertical grid lines at xat positions
    abline(v = xat, lty = grid_lty, col = grid_col, lwd = 0.8)
  }

  # Add data points (plot group by group for better control)
  for (i in seq_along(groups)) {
    group_data = df[df$SOURCE == groups[i], ]
    points(
      x = group_data$MILLI_SEC,
      y = group_data[[signal_col]],
      col = col[i],
      pch = pch_vec[i],
      lwd = line_width,
      ...
    )
  }

  # --------------------------
  # Custom X Axis
  # --------------------------
  axis(
    side = 1,
    at = xat,
    labels = xmark,
    las = 1,  # Horizontal axis labels
    tcl = -0.5,  # Tick length
    ...
  )

  # --------------------------
  # Add Legend
  # --------------------------
  if (add_leg && n_groups > 0) {
    # Handle custom legend positions
    if (is.character(legend_pos)) {
      legend_pos = match.arg(
        legend_pos,
        choices = c("topleft", "topright", "bottomleft", "bottomright",
                   "top", "bottom", "left", "right", "center")
      )
    }

    legend(
      x = legend_pos,
      legend = groups,
      col = col,
      pch = pch_vec,
      cex = leg_cex,
      pt.cex = leg_point_cex,
      pt.lwd = line_width,
      bty = leg_bty,
      bg = if (leg_bty == "o") adjustcolor("white", alpha.f = 0.8) else NA,
      box.lwd = if (leg_bty == "o") 1 else 0,
      inset = 0.02,  # Small inset to prevent legend from being cut off
      ...
    )
  }

  # --------------------------
  # Add Sample Labels (Optional)
  # --------------------------
  if (show_labels) {
    for (i in seq_along(groups)) {
      group_data = df[df$SOURCE == groups[i], ]
      # Place label at the last data point (or maximum time point)
      label_point = group_data[which.max(group_data$MILLI_SEC), ]
      text(
        x = label_point$MILLI_SEC,
        y = label_point[[signal_col]] + label_offset,
        labels = groups[i],
        cex = label_cex,
        col = col[i],
        adj = 0,  # Left-align text
        ...
      )
    }
  }

  # --------------------------
  # Add Reference Lines (Optional)
  # --------------------------
  # Add F0 reference line (dashed gray)
  f0_values = tapply(df[[signal_col]], df$SOURCE, function(x) x[which.min(df$MILLI_SEC)])
  if (length(unique(f0_values)) > 0) {
    abline(h = mean(f0_values), col = adjustcolor("gray50", alpha.f = 0.5), lty = 2, lwd = 0.8)
    text(x = max(xlim), y = mean(f0_values), labels = "F0", 
         col = adjustcolor("gray50", alpha.f = 0.7), cex = 0.7, adj = 1)
  }

  invisible(df)  # Return data frame invisibly for chaining
}

